package com.baseproject.eventbus;

import android.os.Handler;
import android.os.Looper;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by hp on 2017/3/23.
 */

public class EventBus {
    public static EventBus instance = null;
    private Map<Object, List<SubscribeMethod>> cacheMap;
    private Handler handler;
    private ExecutorService executorService;

    private EventBus() {
        cacheMap = new HashMap<>();
        handler = new Handler(Looper.getMainLooper());
        executorService = Executors.newCachedThreadPool();
    }

    public static EventBus getDefault() {
        if (null == instance) {
            synchronized (EventBus.class) {
                if (null == instance) {
                    instance = new EventBus();
                }
            }
        }
        return instance;
    }

    public void register(Object activity) {
        List<SubscribeMethod> list = cacheMap.get(activity);
        if (null == list) {
            List<SubscribeMethod> methods = getSubscibeMethods(activity);
            cacheMap.put(activity, methods);
        }
    }

    private List<SubscribeMethod> getSubscibeMethods(Object activity) {
        List<SubscribeMethod> list = new CopyOnWriteArrayList<>();
        Class<?> clazz = activity.getClass();
        while (clazz != null) {
            String name = clazz.getName();
            if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {
                break;
            }
            Method[] methods = clazz.getDeclaredMethods();
            /**
             * 遍历一个订阅者里的方法，找出含有注解的响应函数
             */
            for (Method method : methods) {
                Subscribe subscribe = method.getAnnotation(Subscribe.class);
                if (null == subscribe) {
                    continue;
                }
                Class<?>[] params = method.getParameterTypes();
                if (params.length == 1) {
                    Class<?> paramsClass = params[0];
                    ThreadMode threadMode = subscribe.threadMode();
                    SubscribeMethod subscribeMethod = new SubscribeMethod(method, threadMode, paramsClass);
                    list.add(subscribeMethod);
                } else {
                    throw new RuntimeException("eventbus only support one params");
                }
            }
            clazz = clazz.getSuperclass();
        }
        return list;
    }

    public void unRegister(Object activity) {
        List<SubscribeMethod> list = cacheMap.get(activity);
        if (null != list) {
            cacheMap.remove(activity);
        }
    }

    public void post(final Object object) {
        Set<Object> set = cacheMap.keySet();
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            final Object activity = iterator.next();
            List<SubscribeMethod> list = cacheMap.get(activity);
            for (final SubscribeMethod subscribeMethod : list) {
                if (object.getClass().isAssignableFrom(subscribeMethod.getEventType())) {
                    switch (subscribeMethod.getThreadMode()) {
                        case PostThread:
                            invoke(object, activity, subscribeMethod);
                            break;
                        case MainThread:
                            if (Looper.myLooper() == Looper.getMainLooper()) {
                                invoke(object, activity, subscribeMethod);
                            } else {
                                handler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        invoke(object, activity, subscribeMethod);
                                    }
                                });
                            }
                            break;
                        case BackgroundThread:
                            if (Looper.myLooper() != Looper.getMainLooper()) {
                                invoke(object, activity, subscribeMethod);
                            } else {
                                executorService.execute(new Runnable() {
                                    @Override
                                    public void run() {
                                        invoke(object, activity, subscribeMethod);
                                    }
                                });
                            }
                    }
                }
            }
        }
    }

    private void invoke(Object object, Object activity, SubscribeMethod subscribeMethod) {
        try {
            subscribeMethod.getMethod().invoke(activity, object);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
